import datetime
import logging
import os

import google.protobuf.json_format as json_format
import pytz
from absl import (app, flags)

from coin.base.datetime_util import (
    convert_string_to_date,
    to_timestamp_int)
from coin.exchange.base.strategy_logger import StrategyLogProvider
from coin.exchange.kr_rest.product.product_impl import generate_product_from_str2
from coin.proto.coin_executor_pb2 import PassiveExecutorConfig
from coin.proto.coin_order_gateway_pb2 import (
    OrderEvent,
    OrderGatewayLog)
from coin.proto.coin_query_pb2 import (
    AccountBalance,
    AccountPosition,
    CurrencyBalance,
    ProductPosition)
from coin.proto.coin_request_pb2 import (
    AccountRequestProto,
    ProcessInfoProto,
    StrategyRequestProto)
from coin.proto.coin_strategy_pb2 import StrategyLog
from fastfeature.simulator_spec_pb2 import (
    OrderResponsesProto,
    SingleResponseProto)


def _validate_sim_response(account_request, order_response):
  assert account_request.HasField('market_type')
  assert account_request.HasField('exchange')
  assert account_request.HasField('owner')
  symbol_info = order_response.symbol_info
  assert symbol_info.HasField('product_name')
  assert symbol_info.HasField('reserve')
  assert symbol_info.market_type == account_request.market_type
  assert symbol_info.exchange == account_request.exchange


def _gen_log_timestamp(market_type, exchange, symbol, trading_date):
  min_dt = datetime.datetime.combine(trading_date, datetime.time.min)
  try:
    generate_product_from_str2(market_type, exchange, None, symbol, min_dt)
    return to_timestamp_int(min_dt.replace(tzinfo=pytz.UTC))
  except:
    pass
  if exchange in ('Huobi', 'Okex'):
    rollover_dt = min_dt + datetime.timedelta(8, 0, 0)
    try:
      generate_product_from_str2(market_type, exchange, None, symbol, rollover_dt)
      return to_timestamp_int(rollover_dt.replace(tzinfo=pytz.UTC))
    except:
      pass
  logging.error('Fail to generate product: %s, %s, %s, %s',
                market_type, exchange, symbol, trading_date)
  return None


def _gen_oe_og_log(account_request, order_response):
  oe_og_logs = []
  for single_response in order_response.responses:
    response_dict = json_format.MessageToDict(
        single_response, preserving_proto_field_name=True)
    response_dict['symbol'] = order_response.symbol_info.product_name
    response_dict['event_time'] = response_dict['time']
    order_side = None
    sign = response_dict.get('sign')
    order_side_map = {1: 'BUY_ORDER', -1: 'SELL_ORDER'}
    response_dict['order_side'] = order_side_map.get(sign)
    order_id = response_dict.get('order_id')
    for key in ('proc_order_id', 'internal_order_id', 'external_order_id'):
      response_dict[key] = order_id
    fill_type_map = {
        'UNKNOWN_FILL': 'UNKNOWN_FILL_TYPE',
        'MAKER': 'MAKER_FILL_TYPE',
        'TAKER': 'TAKER_FILL_TYPE'}
    fill_type = response_dict.get('fill_type')
    response_dict['fill_type'] = fill_type_map.get(fill_type)

    oe = OrderEvent()
    json_format.ParseDict(response_dict, oe, ignore_unknown_fields=True)
    oe_og_logs.append(OrderGatewayLog(
        type=OrderGatewayLog.ORDER_EVENT,
        event=oe,
        account_request=account_request))
  return oe_og_logs


def _dump_strat_log(strategy_logger, trading_date, account_request, sim_results):
  min_timestamp = to_timestamp_int(datetime.datetime.combine(
      trading_date, datetime.time.min.replace(tzinfo=pytz.UTC)))
  max_timestamp = to_timestamp_int(datetime.datetime.combine(
      trading_date, datetime.time.max.replace(tzinfo=pytz.UTC)))
  market_type = account_request.market_type
  exchange = account_request.exchange
  each_strat_log = []
  each_reserve = []
  each_position = []
  each_executor_config = []
  each_oe_og_log = []
  for sim_result in sim_results:
    for response in sim_result.order_responses:
      _validate_sim_response(account_request, response)
      symbol_info = response.symbol_info
      product_name = symbol_info.product_name
      reserve = symbol_info.reserve
      if account_request.market_type == 'Spot':
        currency = product_name.split('-')[0]
        each_reserve.append(
            CurrencyBalance(currency=currency, total=reserve))
      else:
        # TODO(chensili): use prev position from pta db
        each_position.append(
            ProductPosition(symbol=product_name, net_position=0))
      # since min/max pos is not given in symbol info, use -1/+1 for workaround
      each_executor_config.append(PassiveExecutorConfig(
          symbol=product_name, min_pos=reserve-1, max_pos=reserve+1))
      oe_og_log = _gen_oe_og_log(account_request, response)
      each_oe_og_log.extend(oe_og_log)

  strat_log = StrategyLog(
      type=StrategyLog.STRAT_REQUEST, request=strategy_logger.request)
  each_strat_log.append((strat_log, min_timestamp))
  for executor_config in each_executor_config:
    log_ts = _gen_log_timestamp(
        market_type, exchange, executor_config.symbol, trading_date)
    if log_ts is not None:
      strat_log = StrategyLog(
          type=StrategyLog.PASS_EXECUTOR_CONFIG,
          pass_executor_config=executor_config,
          account_request=account_request)
      each_strat_log.append((strat_log, log_ts))
  if len(each_reserve) > 0:
    reserve = AccountBalance(
        market_type=market_type, exchange=exchange,
        each_balance=each_reserve)
    # TODO(chensili): use prev balance from pta db
    balance_og_log = OrderGatewayLog(
        type=OrderGatewayLog.BALANCE,
        balance=reserve,
        account_request=account_request)
    strat_log = StrategyLog(
        type=StrategyLog.RESERVE,
        reserve=reserve,
        account_request=account_request)
    each_strat_log.append((strat_log, min_timestamp))
    strat_log = StrategyLog(type=StrategyLog.OG_LOG, og_log=balance_og_log)
    each_strat_log.append((strat_log, min_timestamp))
  for product_position in each_position:
    log_ts = _gen_log_timestamp(
        market_type, exchange, product_position.symbol, trading_date)
    if log_ts is not None:
      position = AccountPosition(
          market_type=market_type, exchange=exchange, each_position=[product_position])
      position_og_log = OrderGatewayLog(
          type=OrderGatewayLog.POSITION,
          position=position,
          account_request=account_request)
      strat_log = StrategyLog(type=StrategyLog.OG_LOG, og_log=position_og_log)
      each_strat_log.append((strat_log, log_ts))
  for oe_og_log in each_oe_og_log:
    strat_log = StrategyLog(type=StrategyLog.OG_LOG, og_log=oe_og_log)
    each_strat_log.append((strat_log, oe_og_log.event.event_time))
  each_strat_log.sort(key=lambda x: x[1])

  # remove existing sim strat log first
  filepath = strategy_logger._writer.filepath
  if os.path.exists(filepath):
    os.remove(filepath)
  for strat_log, log_ts in each_strat_log:
    if min_timestamp <= log_ts <= max_timestamp:
      strategy_logger.write(strat_log, flush=True, timestamp=log_ts)


def convert_sim_result_to_strat_log(
    *, trading_date, strategy_request, process_info, account_request,
    sim_results, output_dir):
  assert isinstance(strategy_request, StrategyRequestProto)
  assert isinstance(process_info, ProcessInfoProto)
  assert isinstance(account_request, AccountRequestProto)
  assert strategy_request.HasField('strategy_name')
  assert process_info.HasField('machine')

  log_ts = to_timestamp_int(datetime.datetime.combine(
      trading_date, datetime.time.min.replace(tzinfo=pytz.UTC)))
  strategy_logger = StrategyLogProvider(
      strategy_request,
      log_root=output_dir,
      machine=process_info.machine)
  _dump_strat_log(strategy_logger, trading_date, account_request, sim_results)


def main(_):
  FLAGS = flags.FLAGS
  strategy_request = StrategyRequestProto(strategy_name='lm_agg2_huobi_futures_btc')
  process_info = ProcessInfoProto(machine='strategy-43.ap-northeast-1.huobi')
  trading_date = convert_string_to_date('20200529')
  account_request = AccountRequestProto(
      market_type='Futures', exchange="Huobi", owner='prestoinvt53')
  output_dir = '~/data/sim_strat_proto_log'
  pb_paths = ['/tmp/tmpbttva8q4/20200529T000000Futures.Huobi.v1.pb',
              '/tmp/tmpbttva8q4/20200529T081500Futures.Huobi.v1.pb']
  responses = []
  for pb_path in pb_paths:
    with open(pb_path, 'rb') as fread:
      response = OrderResponsesProto()
      response.ParseFromString(fread.read())
      responses.append(response)
  convert_sim_result_to_strat_log(
      trading_date=trading_date,
      strategy_request=strategy_request,
      process_info=process_info,
      account_request=account_request,
      sim_results=responses,
      output_dir=os.path.abspath(os.path.normpath(os.path.expanduser(output_dir))))


if __name__ == '__main__':
  app.run(main)
